﻿// -----------------------------------------------------------------------
// <copyright file="ServiceManager.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
// -----------------------------------------------------------------------
#region Usings

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.Policy;
using System.Threading;
using GateShift.Servers.GameServer.Configuration.Services;
using GateShift.Servers.GameServer.LoggingSystem;
using GateShift.Servers.GameServer.Managers.Connection;
using GateShift.Servers.GameServer.Properties;
using GateShift.Services.SDK.Services;
using GateShift.Services.SDK.Services.Events;
using GateShift.Services.SDK.Services.HostIntegration;
using GateShift.Services.SDK.Services.Hosts;
using GateShift.Services.SDK.Services.Interfaces;

#endregion

namespace GateShift.Servers.GameServer.Managers.Service
{
    /// <summary>
    /// Management Class for all game services
    /// </summary>
    internal class ServiceManager : IServiceHost, IServiceManager, ICommandLineInterfaceHandler
    {
        #region Variables

        private readonly Dictionary<Type, IService> _services;
        private readonly Dictionary<string, ICommandLineInterfaceHandler> _serviceCommandLines;
        private Thread _serviceManagerThread;
        private ServiceConfig _serviceConfig;

        #endregion

        #region CTor/DTor

        /// <summary>
        /// Initializes a new instance of the ServiceManager class
        /// </summary>
        public ServiceManager()
        {
            _services = new Dictionary<Type, IService>();
            _serviceCommandLines = new Dictionary<string, ICommandLineInterfaceHandler>();
        }

        #endregion

        #region Events

        /// <summary>
        /// Event for a service initializing
        /// </summary>
        public event EventHandler<ServiceInitializedEventArgs> ServiceInitialized;

        /// <summary>
        /// Event for a service's state changing
        /// </summary>
        public event EventHandler<ServiceStateChangedEventArgs> ServiceStateChanged;

        #endregion

        #region IServiceManager Members

        /// <summary>
        /// Gets or sets the ServerHost to pass to the Services
        /// </summary>
        public IServerHost ServerHostManager { get; set; }

        /// <summary>
        /// Gets or sets the reference to the connection manager for sending data to the
        /// clients and recieving data from the clients
        /// </summary>
        public IConnectionManager ConnectionManager { get; set; }

        #endregion

        /// <summary>
        /// Gets a value indicating whether or not the manager status shows it is running
        /// </summary>
        public bool IsAlive
        {
            get
            {
                return _serviceManagerThread != null && _serviceManagerThread.IsAlive;
            }
        }

        #region ICommandLineInterfaceHandler Members

        /// <summary>
        /// Handles commands destined for the Service Manager
        /// </summary>
        /// <param name="arguments">List of arguments parsed from the command handler</param>
        /// <returns>Boolean based on whether or not the command was successfully handled</returns>
        bool ICommandLineInterfaceHandler.HandleCommand(List<string> arguments)
        {
            if (arguments == null)
            {
                throw new ArgumentNullException("arguments");
            }

            // If there is nothing to process let the requestor know
            if (arguments.Count < 1)
            {
                return false;
            }

            List<string> subArguments = (arguments.Count > 1) ? arguments.GetRange(1, arguments.Count - 1) : new List<string>();

            switch (arguments[0])
            {
                case "config":
                    return HandleConfigCommands(subArguments);

                case "name":
                    return HandleServiceCommands(subArguments);

                case "state":
                    return HandleStateCommands(subArguments);
            }

            return false;
        }

        #endregion

        #region IServiceHost Members

        /// <summary>
        /// Locates a specified service for another service to utilize
        /// </summary>
        /// <typeparam name="T">IService based type to retrieve a reference of</typeparam>
        /// <returns>Requested reference of IService or NULL if a service by that type does not exist</returns>
        public T LocateService<T>() where T : IService
        {
            if (_services.ContainsKey(typeof(T)))
            {
                return (T)_services[typeof(T)];
            }

            if (ConnectionManager != null && Settings.Default.AllowRemoteServiceConnections)
            {
                return ConnectionManager.OpenServiceChannel<T>();
            }

            return default(T);
        }

        /// <summary>
        /// Registers a command line handler for administrative commands
        /// </summary>
        /// <param name="rootCommand">Root command to trigger this handler</param>
        /// <param name="handler">ICommandLineInterfaceHandler to register</param>
        /// <exception cref="NullReferenceException">Thrown if handler is null</exception>
        /// <exception cref="InvalidOperationException">Thrown if a handler is already registered with the root command</exception>
        public void RegisterServiceCommandHandler(string rootCommand, ICommandLineInterfaceHandler handler)
        {
            if (string.IsNullOrEmpty(rootCommand))
            {
                throw new ArgumentNullException("rootCommand", "RootCommand cannot be null or empty");
            }

            if (_serviceCommandLines.ContainsKey(rootCommand.ToLower()))
            {
                throw new InvalidOperationException("Root Command Already Exists");
            }

            _serviceCommandLines.Add(rootCommand.ToLower(), handler);
        }

        #endregion

        #region Service Management

        /// <summary>
        /// Handles starting of a service
        /// </summary>
        /// <param name="serviceName">ServiceName to start</param>
        public void StateServiceStart(string serviceName)
        {
            foreach (IService service in _services.Values)
            {
                if (service.GetType().Name.ToLower() != serviceName.ToLower())
                {
                    continue;
                }

                if (!(service is IThreadedService))
                {
                    continue;
                }

                if (((IThreadedService)service).Start())
                {
                    // TODO Log success
                }
            }
        }

        /// <summary>
        /// Handles stopping of a service
        /// </summary>
        /// <param name="serviceName">ServiceName to start</param>
        public void StateServiceStop(string serviceName)
        {
            foreach (IService service in _services.Values)
            {
                if (service.GetType().Name.ToLower() != serviceName.ToLower())
                {
                    continue;
                }

                if (!(service is IThreadedService))
                {
                    continue;
                }

                if (((IThreadedService)service).Stop())
                {
                    // TODO Log success
                }
            }
        }

        #endregion

        #region IManager Members

        /// <summary>
        /// Starts the manager background thread
        /// </summary>
        public void Start()
        {
            if (IsAlive)
            {
                throw new InvalidOperationException("Stop must be called before restarting");
            }

            _serviceManagerThread = new Thread(ServiceManagerThread)
                                        {
                                            Name = "Service Manager",
                                            Priority = ThreadPriority.BelowNormal
                                        };

            Log.AddEntry("Starting Manager", "ServiceManager", LogPriorities.Informative, TraceEventType.Start, (int)CoreLogCategories.ServiceManager);

            _serviceManagerThread.Start();
        }

        /// <summary>
        /// Stops the manager background thread
        /// </summary>
        public void Stop()
        {
            if (IsAlive)
            {
                Log.AddEntry("Stopping Manager", "ServiceManager", LogPriorities.Informative, TraceEventType.Stop, (int)CoreLogCategories.ServiceManager);
                _serviceManagerThread.Abort();
                _serviceManagerThread.Join(10000);
            }

            _serviceManagerThread = null;
        }

        /// <summary>
        /// Initializes all manager resources
        /// </summary>
        public void Initialize()
        {
            Log.AddEntry("Initializing Manager", "ServiceManager", LogPriorities.Informative, TraceEventType.Verbose, (int)CoreLogCategories.ServiceManager);
        }

        #endregion

        #region EventRaisers

        /// <summary>
        /// Event raiser for ServiceInitialized
        /// </summary>
        /// <param name="e">ServiceInitializedEventArgs with information about the event</param>
        protected void OnServiceInitialized(ServiceInitializedEventArgs e)
        {
            if (ServiceInitialized != null)
            {
                ServiceInitialized(this, e);
            }
        }

        /// <summary>
        /// Event raiser for ServiceStateChanged
        /// </summary>
        /// <param name="e">ServiceStateChangedEventArgs with information about the event</param>
        protected void OnServiceStateChanged(ServiceStateChangedEventArgs e)
        {
            if (ServiceStateChanged != null)
            {
                ServiceStateChanged(this, e);
            }
        }

        #endregion

        #region Loading Files

        /// <summary>
        /// Checks the specified file to verify it is a valid Service assembly
        /// </summary>
        /// <param name="file">Path to the file to test</param>
        /// <returns>bool based on the success of the test</returns>
        private static bool TestFile(string file)
        {
            bool returnValue = false;

            // Set up the new domain to be just like this one
            AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
            Evidence evidence = AppDomain.CurrentDomain.Evidence;

            // Set the setup.PrivateBinPath if the dependencies are in another location
            AppDomain appDomain = AppDomain.CreateDomain(string.Format("GSTest: {0}.{1}", Path.GetFileNameWithoutExtension(file), DateTime.Now.ToFileTime()), evidence, setup);

            RemotingTester remote = null;

            if (Assembly.GetExecutingAssembly().FullName != null)
            {
                remote = (RemotingTester)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(RemotingTester).FullName);
            }

            // If we failed then log the end results
            if (remote != null)
            {
                if (!Path.IsPathRooted(file))
                {
                    file = Path.Combine(appDomain.BaseDirectory, file);
                }

                RemotingTester.LoadStatus reason = remote.TestLoad(file);
                switch (reason)
                {
                    default:
                        Log.AddEntry("Service Failure", string.Format("Reason: ", reason), LogPriorities.Warning, TraceEventType.Warning, (int)CoreLogCategories.ConsoleManager);
                        break;
                    case RemotingTester.LoadStatus.Successful:
                        returnValue = true;
                        break;
                }
            }

            // Perform temporary cleanup here
            AppDomain.Unload(appDomain);

            // Return the success
            return returnValue;
        }

        /// <summary>
        /// Loads an assembly and gets a list of all service types to create
        /// </summary>
        /// <param name="file">Path to the file to load from</param>
        /// <returns>Dictionary of IService Type and Actual Type for all available services</returns>
        private static Dictionary<Type, Type> RetrieveServiceList(string file)
        {
            if (file == null)
            {
                throw new ArgumentNullException("file");
            }

            Dictionary<Type, Type> returnList = null;

            if (!Path.IsPathRooted(file))
            {
                file = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file);
            }

            // Try to load it in our sandbox first
            if (TestFile(file))
            {
                // Initialize our dictionary of services
                returnList = new Dictionary<Type, Type>();
                Assembly asm = Assembly.LoadFile(file);

                foreach (Type t in asm.GetTypes())
                {
                    // We can only handle further code if we are assignable to an IService
                    if (!typeof(IService).IsAssignableFrom(t))
                    {
                        continue;
                    }

                    // We want to find the attribute LoadableServiceAttribute to specify this is a valid service
                    foreach (object attribute in t.GetCustomAttributes(false))
                    {
                        if ((attribute as LoadableServiceAttribute) == null)
                        {
                            continue;
                        }

                        // Add the type to our list with the attribute's hosting type as the key
                        returnList.Add(((LoadableServiceAttribute)attribute).ServiceType, t);
                    }
                }
            }

            return returnList;
        }

        #endregion

        #region Configuration Methods

        /// <summary>
        /// Loads all service configuration
        /// </summary>
        private void LoadConfiguration()
        {
            Log.AddEntry("Loading File", Settings.Default.ServiceConfigFile, LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);
            _serviceConfig = ServiceConfig.Load(Settings.Default.ServiceConfigFile);
        }

        /// <summary>
        /// Creates instances of all services
        /// </summary>
        private void LoadServices()
        {
            // We want to go through all the config items and load the files.
            foreach (ServiceFile file in _serviceConfig.Files)
            {
                // If the file is disabled, skip it
                if (!file.Enabled)
                {
                    continue;
                }

                IService instance = null;
                Dictionary<Type, Type> servicesToLoad = RetrieveServiceList(file.Path);

                // If the file did not return any services, skip it and log it
                if (servicesToLoad == null)
                {
                    Log.AddEntry("Invalid Service File", string.Format("File {0} failed to load any services. Skipping Service Loading for this file.", file.Path), LogPriorities.Warning, TraceEventType.Warning, (int)CoreLogCategories.ConsoleManager);
                    continue;
                }

                // Loop through all the loadable services
                foreach (KeyValuePair<Type, Type> item in servicesToLoad)
                {
                    // If we already loaded an item by it's service interface, skip it
                    if (_services.ContainsKey(item.Key))
                    {
                        continue;
                    }

                    // We want to ignore the requested ignored service types for this file
                    bool skipType = false;

                    foreach (IgnoredService ignore in file.IgnoredServices)
                    {
                        if (item.Key.GetType().Name == ignore.Service)
                        {
                            skipType = true;
                        }
                    }

                    if (skipType)
                    {
                        continue;
                    }

                    // Lets try and find an existing instance of the service
                    foreach (KeyValuePair<Type, IService> service in _services)
                    {
                        if (service.Value.GetType() != item.Value)
                        {
                            continue;
                        }

                        instance = service.Value;
                        break;
                    }

                    // If an existing instance was not found, create one
                    if (instance == null)
                    {
                        instance = (IService)Activator.CreateInstance(item.Value);
                    }

                    // Lets make sure Activator created the instance
                    if (instance != null)
                    {
                        instance.ServerHost = ServerHostManager;
                        instance.Initialized += IService_Initialized;

                        if (instance is IThreadedService)
                        {
                            ((IThreadedService)instance).StateChanged += IService_StateChanged;
                        }

                        _services.Add(item.Key, instance);

                        if (ConnectionManager != null && Settings.Default.AllowRemoteServiceConnections)
                        {
                            ConnectionManager.AddServiceHost(item.Key.Name, instance);
                        }
                    }

                    instance = null;
                }
            }
        }

        /// <summary>
        /// Starts all services that can be started
        /// </summary>
        private void StartServices()
        {
            foreach (IService service in _services.Values)
            {
                StateServiceStart(service.GetType().ToString());
            }
        }

        /// <summary>
        /// Initializes all services
        /// </summary>
        private void InitializeServices()
        {
            foreach (IService service in _services.Values)
            {
                service.Initialize();
            }
        }

        /// <summary>
        /// Stops all services that can be stopped
        /// </summary>
        private void StopServices()
        {
            foreach (IService service in _services.Values)
            {
                StateServiceStop(service.GetType().ToString());
            }
        }

        #endregion

        #region Manager Functions

        /// <summary>
        /// Main background thread for Service Manager
        /// </summary>
        private void ServiceManagerThread()
        {
            // Catch thread aborting
            try
            {
                Log.AddEntry("Thread Started", "Service Manager Thread", LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);

                Log.AddEntry("Loading Configuration", "Service Manager Configuration", LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);
                LoadConfiguration();

                Log.AddEntry("Loading Services", "Service Manager Services", LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);
                LoadServices();

                Log.AddEntry("Starting Services", "Service Manager Services", LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);
                StartServices();

                Log.AddEntry("Initializing Services", "Service Manager Services", LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);
                InitializeServices();

                Thread.Sleep(Timeout.Infinite);
            }
            catch (ThreadAbortException)
            {
                Log.AddEntry("Thread Stopping", "Service Manager Thread", LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);

                Log.AddEntry("Stopping Services", "Service Manager Services", LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);
                StopServices();

                if (ConnectionManager != null && Settings.Default.AllowRemoteServiceConnections)
                {
                    foreach (KeyValuePair<Type, IService> service in _services)
                    {
                        ConnectionManager.RemoveServiceHost(service.Key.Name);
                    }
                }
            }
        }

        #endregion

        #region Command Group Handlers

        /// <summary>
        /// Handler for all Service Name ... commands
        /// </summary>
        /// <param name="arguments">List of arguments to handle</param>
        /// <returns>bool based on whether or not the command was successfully handled</returns>
        private bool HandleServiceCommands(List<string> arguments)
        {
            if (arguments == null)
            {
                throw new ArgumentNullException("arguments");
            }

            // If there is nothing to process let the requestor know
            if (arguments.Count < 1)
            {
                return false;
            }

            List<string> subArguments = (arguments.Count > 1) ? arguments.GetRange(1, arguments.Count - 1) : new List<string>();

            return _serviceCommandLines.ContainsKey(arguments[0].ToLower()) &&
                   _serviceCommandLines[arguments[0].ToLower()].HandleCommand(subArguments);
        }

        /// <summary>
        /// Handler for all Service Config ... commands
        /// </summary>
        /// <param name="arguments">List of arguments to handle</param>
        /// <returns>bool based on whether or not the command was successfully handled</returns>
        private bool HandleConfigCommands(IList<string> arguments)
        {
            if (arguments == null)
            {
                throw new ArgumentNullException("arguments");
            }

            // If there is nothing to process let the requestor know
            if (arguments.Count < 1)
            {
                return false;
            }

            // Switch based on the sub config command
            switch (arguments[0].ToLower())
            {
                case "add":
                    if (arguments.Count < 2)
                    {
                        return false;
                    }

                    ConfigAddFile(arguments[1]);
                    break;

                case "remove":
                    if (arguments.Count < 2)
                    {
                        return false;
                    }

                    ConfigRemoveFile(arguments[1]);
                    break;

                case "enable":
                    if (arguments.Count < 2)
                    {
                        return false;
                    }

                    ConfigEnableFile(arguments[1], true);
                    break;

                case "disable":
                    if (arguments.Count < 2)
                    {
                        return false;
                    }

                    ConfigEnableFile(arguments[1], false);
                    break;

                case "ignore":
                    if (arguments.Count < 3)
                    {
                        return false;
                    }

                    ConfigIgnoreService(arguments[1], arguments[2], true);
                    break;

                case "unignore":
                    if (arguments.Count < 3)
                    {
                        return false;
                    }

                    ConfigIgnoreService(arguments[1], arguments[2], true);
                    break;

                case "commit":
                    // TODO Log that the save was successful or not
                    _serviceConfig.Save(Settings.Default.ServiceConfigFile);
                    break;

                default:
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Handler for all Service State ... commands
        /// </summary>
        /// <param name="arguments">List of arguments to handle</param>
        /// <returns>Boolean based on whether or not the command was successfully handled</returns>
        private bool HandleStateCommands(IList<string> arguments)
        {
            if (arguments == null)
            {
                throw new ArgumentNullException("arguments");
            }

            // If there is nothing to process let the requestor know
            if (arguments.Count < 1)
            {
                return false;
            }

            // Switch based on the sub config command
            switch (arguments[0].ToLower())
            {
                case "start":
                    if (arguments.Count < 2)
                    {
                        return false;
                    }

                    StateServiceStart(arguments[1]);
                    break;

                case "stop":
                    if (arguments.Count < 2)
                    {
                        return false;
                    }

                    StateServiceStop(arguments[1]);
                    break;

                default:
                    return false;
            }

            return true;
        }

        #endregion

        #region Configuration Modification

        /// <summary>
        /// Handles adding of a service file to the configuration
        /// </summary>
        /// <param name="path">Path of the file to add</param>
        private void ConfigAddFile(string path)
        {
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            // We want to hunt down duplicate file names from any path
            foreach (ServiceFile file in _serviceConfig.Files)
            {
                if (Path.GetFileNameWithoutExtension(file.Path) != Path.GetFileNameWithoutExtension(path))
                {
                    continue;
                }

                // TODO : Log that the filename already exists
                return;
            }

            // Make sure the file is valid
            if (Path.GetFileName(path).IndexOfAny(Path.GetInvalidFileNameChars()) > -1 ||
                Path.GetDirectoryName(path).IndexOfAny(Path.GetInvalidPathChars()) > -1)
            {
                // TODO : Log that the path is invalid
                return;
            }

            // Make sure the file exists
            if (!File.Exists(path))
            {
                // TODO : Log that the path does not exist
                return;
            }

            // TODO : Log that it was added
            _serviceConfig.Files.Add(new ServiceFile(path));
        }

        /// <summary>
        /// Handles removing of a service file to the configuration
        /// </summary>
        /// <param name="fileName">Name of the file to remove</param>
        private void ConfigRemoveFile(string fileName)
        {
            if (fileName == null)
            {
                throw new ArgumentNullException("fileName");
            }

            ServiceFile foundFile = null;

            // We want to hunt down duplicate file names from any path
            foreach (ServiceFile file in _serviceConfig.Files)
            {
                if (Path.GetFileNameWithoutExtension(file.Path) != fileName)
                {
                    continue;
                }

                foundFile = file;
                break;
            }

            if (foundFile == null)
            {
                // TODO Log that the file was not found in the list
                return;
            }

            // TODO : Log that it was removed
            _serviceConfig.Files.Remove(foundFile);
        }

        /// <summary>
        /// Handles enabling/disabling of a service file in the configuration
        /// </summary>
        /// <param name="fileName">Name of the file to change</param>
        /// <param name="newState">New enabled state of the file</param>
        private void ConfigEnableFile(string fileName, bool newState)
        {
            if (fileName == null)
            {
                throw new ArgumentNullException("fileName");
            }

            ServiceFile foundFile = null;

            // We want to hunt down duplicate file names from any path
            foreach (ServiceFile file in _serviceConfig.Files)
            {
                if (Path.GetFileNameWithoutExtension(file.Path) != fileName)
                {
                    continue;
                }

                foundFile = file;
                break;
            }

            if (foundFile == null)
            {
                // TODO Log that the file was not found in the list
                return;
            }

            if (foundFile.Enabled == newState)
            {
                // TODO Log that the file state is the same as the new value
                return;
            }

            foundFile.Enabled = newState;
        }

        /// <summary>
        /// Handles ignoring of a service in a service file for the configuration
        /// </summary>
        /// <param name="fileName">Name of the file to change</param>
        /// <param name="serviceName">Type Name of the service to ignore</param>
        /// <param name="newState">New ignored state of the service</param>
        private void ConfigIgnoreService(string fileName, string serviceName, bool newState)
        {
            if (fileName == null)
            {
                throw new ArgumentNullException("fileName");
            }

            ServiceFile foundFile = null;
            IgnoredService foundService = null;

            // We want to hunt down duplicate file names from any path
            foreach (ServiceFile file in _serviceConfig.Files)
            {
                if (Path.GetFileNameWithoutExtension(file.Path) != fileName)
                {
                    continue;
                }

                foundFile = file;
                break;
            }

            if (foundFile == null)
            {
                // TODO Log that the file was not found in the list
                return;
            }

            // We want to hunt down duplicate file names from any path
            foreach (IgnoredService service in foundFile.IgnoredServices)
            {
                if (service.Service != serviceName)
                {
                    continue;
                }

                foundService = service;
                break;
            }

            if (newState)
            {
                if (foundService != null)
                {
                    // TODO Log that the service exists
                    return;
                }

                foundFile.IgnoredServices.Add(new IgnoredService(serviceName));
            }
            else
            {
                if (foundService == null)
                {
                    // TODO Log that the service was not found
                    return;
                }

                foundFile.IgnoredServices.Remove(foundService);
            }

            // TODO Log Success
        }

        #endregion

        #region Service Events

        /// <summary>
        /// Event handler for a service finishing its initialization
        /// </summary>
        /// <param name="sender">IService calling this method</param>
        /// <param name="e">Empty EventArgs</param>
        private void IService_Initialized(object sender, EventArgs e)
        {
            OnServiceInitialized(new ServiceInitializedEventArgs((IService)sender));
        }

        /// <summary>
        /// Event handler for a service changing its current state
        /// </summary>
        /// <param name="sender">IService calling this method</param>
        /// <param name="e">StateChangedEventArgs with information about the event</param>
        private void IService_StateChanged(object sender, StateChangedEventArgs e)
        {
            OnServiceStateChanged(new ServiceStateChangedEventArgs((IService)sender, e.NewState, e.PreviousState));
        }

        #endregion

        #region Service Test Loader

        /*
         * NOTE:
         * 
         * Functions and Properties added to these classes CANNOT
         * reference the types of a loaded assembly directly
         * Doing so would cause the main AppDomain to load a
         * copy. This would then defeat the purpose of Marshalling
         */

        /// <summary>
        /// Gateway class for testing assemblies for being valid
        /// </summary>
        [Serializable]
        private class RemotingTester : MarshalByRefObject
        {
            /// <summary>
            /// Gets an enum for the status of the assembly check process
            /// </summary>
            public enum LoadStatus
            {
                /// <summary>
                /// Service assembly loaded and valid services were found
                /// </summary>
                Successful = 0,

                /// <summary>
                /// Assembly could not be found
                /// </summary>
                FileNotFound = 1,

                /// <summary>
                /// Access to the assembly was denied
                /// </summary>
                SecurityError = 2,

                /// <summary>
                /// Assembly is invalid
                /// </summary>
                BadAssembly = 3,

                /// <summary>
                /// Assembly is missing a dependency
                /// </summary>
                MissingDependent = 4,

                /// <summary>
                /// Assembly does not contain any IServices
                /// </summary>
                InvaildServiceModule = 5
            }

            /// <summary>
            /// Test loads a specified assembly to ensure it is valid
            /// </summary>
            /// <param name="File">File to load and test</param>
            /// <returns>LoadStatus based on the success of loading and testing the assembly</returns>
            public LoadStatus TestLoad(string File)
            {
                AssemblyName assemblyName;
                Assembly asm;

                // This Try/Catch will handle the most common problems and write to the LoadErrorMessage property
                try
                {
                    assemblyName = AssemblyName.GetAssemblyName(File);
                }
                catch (FileNotFoundException)
                {
                    return LoadStatus.FileNotFound;
                }
                catch (System.Security.SecurityException)
                {
                    return LoadStatus.SecurityError;
                }
                catch (BadImageFormatException)
                {
                    return LoadStatus.BadAssembly;
                }

                // If the assembly was found successfully, process it
                if (assemblyName != null)
                {
                    // Load the reference to the assembly
                    asm = AppDomain.CurrentDomain.Load(assemblyName);

                    try
                    {
                        // Loop through all types in the assembly
                        foreach (Type t in asm.GetTypes())
                        {
                            if (!typeof(IService).IsAssignableFrom(t))
                            {
                                continue;
                            }

                            foreach (object attribute in t.GetCustomAttributes(false))
                            {
                                if (attribute != null && attribute is LoadableServiceAttribute)
                                {
                                    return LoadStatus.Successful;
                                }
                            }
                        }
                    }
                    catch (ReflectionTypeLoadException)
                    {
                        return LoadStatus.MissingDependent;
                    }
                }

                // Return our find
                return LoadStatus.InvaildServiceModule;
            }
        }

        #endregion
    }
}